#pragma once

#include <cmath>
#include <float.h>

#include <cftc/typecontainer/list.h>

#include "system/grid/grid.h"
#include "system/grid/ngrid.h"

#define MAX_NUMBER_OF_GRIDS      64

#define SIZE_OF_GRIDS            533.33333f
#define CENTER_GRID_ID           (MAX_NUMBER_OF_GRIDS/2)

#define CENTER_GRID_OFFSET      (SIZE_OF_GRIDS/2)

#define MIN_GRID_DELAY          (MINUTE*IN_MILLISECONDS)
#define MIN_MAP_UPDATE_DELAY    50

#define MAX_NUMBER_OF_CELLS     16
#define SIZE_OF_GRID_CELL       (SIZE_OF_GRIDS/MAX_NUMBER_OF_CELLS)

#define CENTER_GRID_CELL_ID     (MAX_NUMBER_OF_CELLS*MAX_NUMBER_OF_GRIDS/2)
#define CENTER_GRID_CELL_OFFSET (SIZE_OF_GRID_CELL/2)

#define TOTAL_NUMBER_OF_CELLS_PER_MAP    (MAX_NUMBER_OF_GRIDS*MAX_NUMBER_OF_CELLS)

#define MAP_RESOLUTION 128

#define MAP_SIZE                (SIZE_OF_GRIDS*MAX_NUMBER_OF_GRIDS)
#define MAP_HALFSIZE            (MAP_SIZE/2)

template <typename...Ts> struct TypeList;

template<const unsigned int LIMIT>
struct CoordPair
{
    CoordPair(unsigned int x = 0, unsigned int y = 0) : x_coord(x), y_coord(y) {}
    CoordPair(const CoordPair<LIMIT>& obj) : x_coord(obj.x_coord), y_coord(obj.y_coord) {}
    bool operator==(const CoordPair<LIMIT>& obj) const { return (obj.x_coord == x_coord && obj.y_coord == y_coord); }
    bool operator!=(const CoordPair<LIMIT>& obj) const { return !operator==(obj); }
    CoordPair<LIMIT>& operator=(const CoordPair<LIMIT>& obj)
    {
        x_coord = obj.x_coord;
        y_coord = obj.y_coord;
        return *this;
    }

    void operator<<(const unsigned int val)
    {
        if (x_coord > val)
        {
            x_coord -= val;
        }
        else
        {
            x_coord = 0;
        }
    }

    void operator>>(const unsigned int val)
    {
        if (x_coord + val < LIMIT)
        {
            x_coord += val;
        }
        else
        {
            x_coord = LIMIT - 1;
        }
    }

    void operator-=(const unsigned int val)
    {
        if (y_coord > val)
        {
            y_coord -= val;
        }
        else
        {
            y_coord = 0;
        }
    }

    void operator+=(const unsigned int val)
    {
        if (y_coord + val < LIMIT)
        {
            y_coord += val;
        }
        else
        {
            y_coord = LIMIT - 1;
        }
    }

    CoordPair& normalize()
    {
        x_coord = std::min(x_coord, LIMIT - 1);
        y_coord = std::min(y_coord, LIMIT - 1);
        return *this;
    }

    unsigned int x_coord;
    unsigned int y_coord;
};

typedef CoordPair<MAX_NUMBER_OF_GRIDS> GridPair;
typedef CoordPair<TOTAL_NUMBER_OF_CELLS_PER_MAP> CellPair;

template<class VISITOR, class CONTAINER>
class TypeContainerVisitor
{
public:
    TypeContainerVisitor(VISITOR& v) : i_visitor(v) {}
    void Visit(CONTAINER& c)
    {
        c.template accept<VISITOR>(std::forward<VISITOR>(i_visitor));
    }
    void Visit(const CONTAINER& c) const
    {
        c.template accept<VISITOR>(std::forward<VISITOR>(i_visitor));
    }
private:
    VISITOR& i_visitor;
};

namespace map::grid
{
    template<class RET_TYPE, int CENTER_VAL>
    inline RET_TYPE Compute(float x, float y, float center_offset, float size)
    {
        // calculate and store temporary values in double format for having same result as same mySQL calculations
        double x_offset = (double(x) - center_offset) / size;
        double y_offset = (double(y) - center_offset) / size;

        int x_val = int(x_offset + CENTER_VAL + 0.5);
        int y_val = int(y_offset + CENTER_VAL + 0.5);
        return RET_TYPE(x_val, y_val);
    }

    inline GridPair ComputeGridPair(float x, float y)
    {
        return Compute<GridPair, CENTER_GRID_ID>(x, y, CENTER_GRID_OFFSET, SIZE_OF_GRIDS);
    }

    inline CellPair ComputeCellPair(float x, float y)
    {
        return Compute<CellPair, CENTER_GRID_CELL_ID>(x, y, CENTER_GRID_CELL_OFFSET, SIZE_OF_GRID_CELL);
    }

    inline void NormalizeMapCoord(float& c)
    {
        if (c > MAP_HALFSIZE - 0.5)
        {
            c = MAP_HALFSIZE - 0.5;
        }
        else if (c < -(MAP_HALFSIZE - 0.5))
        {
            c = -(MAP_HALFSIZE - 0.5);
        }
    }

    inline bool IsValidMapCoord(float c)
    {
        return finite(c) && (std::fabs(c) <= MAP_HALFSIZE - 0.5);
    }

    inline bool IsValidMapCoord(float x, float y)
    {
        return IsValidMapCoord(x) && IsValidMapCoord(y);
    }

    inline bool IsValidMapCoord(float x, float y, float z)
    {
        return IsValidMapCoord(x, y) && finite(z);
    }

    inline bool IsValidMapCoord(float x, float y, float z, float o)
    {
        return IsValidMapCoord(x, y, z) && finite(o);
    }
}

#if 1
static float rand_norm_f(void)
{
    return 0.0f;
}
#endif

using GridTypeMapContainer = cftc::typecontainer::List<
    battle::object::GameObject*, 
    battle::object::Creature*/*except pets*/, 
    battle::object::DynamicObject*, 
    battle::object::Corpse*/*bones*/>;

using WorldTypeMapContainer = cftc::typecontainer::List<
    battle::object::Player*, 
    battle::object::Creature*/*pets*/, 
    battle::object::Corpse*/*resurrectable*/, 
    battle::object::Camera*>;

typedef game::system::grid::Grid<battle::object::Player, WorldTypeMapContainer, GridTypeMapContainer> GridType;
typedef game::system::grid::NGrid<MAX_NUMBER_OF_CELLS, battle::object::Player, WorldTypeMapContainer, GridTypeMapContainer> NGridType;
